﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using TByte.XnaHelper.GameObjects;
using TByte.XnaHelper.Input;
using Microsoft.Xna.Framework.Content;
using System.IO;
using System.Collections.ObjectModel;
using System.Xml.Linq;

namespace TByte.XnaHelper
{
    public static class Manager
    {
        #region Fields
        public static Texture2D dummyTexture;
        public static SpriteFont basicFont;
        static ContentManager content;
        static GameTime gameTime;
        static Vector2 screen;
        static Random random = new Random();
        static Dictionary<string, GameState> gameStates = new Dictionary<string, GameState>();
        static string currentGameState = string.Empty;
        static GraphicsDevice graphicsDevice;
        static MouseState previousMouseState;
        static SpriteBatch spriteBatch;
        static Rectangle screenRect;
        static Camera2DGameObject currentCamera;
        static bool takeScreenshot = false;
        static string screenshotFolder;
        static string settingsFile = "settings.xcfg";
        static GraphicsDeviceManager graphicsManager;
        static Dictionary<string, string> settings = new Dictionary<string,string>();
        static Point defaultScreenSize = new Point(1280, 720);
        static Dictionary<string, string> controls = new Dictionary<string, string>(); //Format: Category_ControlName,SettingKey: Example "Toggle Console","key_toggleConsole" (No category is general category)
        #endregion

        #region Methods
        public static void Initialize(GameWindow window, GraphicsDeviceManager graphics, ContentManager contManager, string GameName)
        {
            graphicsDevice = graphics.GraphicsDevice;
            graphicsManager = graphics;
            content = contManager;
            DefineControl("Developer Console", "keys_toggleConsole", Keys.Tab);
            DefineControl("Screenshot", "keys_screenshot", Keys.F4);

            InputSystem.Initialize(window);

            //Create texture
            dummyTexture = new Texture2D(graphics.GraphicsDevice, 1, 1);
            dummyTexture.SetData(new Color[] { Color.White });

            //Save font
            basicFont = Assets.Font("consoleFont");

            //Hook console into events
            InputSystem.CharEntered += new CharEnteredHandler(GameConsole.HandleInput);
            InputSystem.MouseWheel += new MouseEventHandler(GameConsole.MouseWheelInput);
            InputSystem.KeyDown += new KeyEventHandler(GameConsole.HandleKey);

            //Add empty gamestate to prevent null error
            GameState emptyState = new GameState(String.Empty, new GameObject("null root"));
            AddGameState(emptyState);

            //Hook into input events
            InputSystem.CharEntered += new CharEnteredHandler(InputSystem_CharEntered);
            InputSystem.KeyDown += new KeyEventHandler(InputSystem_KeyDown);
            InputSystem.KeyUp += new KeyEventHandler(InputSystem_KeyUp);
            InputSystem.MouseDoubleClick += new MouseEventHandler(InputSystem_MouseDoubleClick);
            InputSystem.MouseDown += new MouseEventHandler(InputSystem_MouseDown);
            InputSystem.MouseHover += new MouseEventHandler(InputSystem_MouseHover);
            //InputSystem.MouseMove += new MouseEventHandler(InputSystem_MouseMove); No mouse move event from input
            InputSystem.MouseUp += new MouseEventHandler(InputSystem_MouseUp);
            InputSystem.MouseWheel += new MouseEventHandler(InputSystem_MouseWheel);
            spriteBatch = new SpriteBatch(graphics.GraphicsDevice);

            //Set screenshot folder
            screenshotFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) + "\\" + GameName + "\\";

            //Create screenshot folder
            if (!Directory.Exists(screenshotFolder))
                Directory.CreateDirectory(screenshotFolder);

            //Load settings
            LoadSettings();
        }
        public static void Update(GameTime newGameTime)
        {
            if (InputSystem.MouseEnabled)
            {
                if (Mouse.GetState().X != previousMouseState.X || Mouse.GetState().Y != previousMouseState.Y)
                {
                    previousMouseState = Mouse.GetState();
                    InputSystem_MouseMove(null, new MouseEventArgs(MouseButton.None, 0, previousMouseState.X, previousMouseState.Y, 0));
                }
            }
            else
            {
                Mouse.SetPosition(Int32.MinValue, Int32.MinValue);
            }
            //Update gametime
            gameTime = newGameTime;

            gameStates[currentGameState].Update();
        }
        public static void Draw()
        {
            graphicsDevice.Clear(Color.Black);
            if (takeScreenshot)
                ScreenShot();
            else
            {
                spriteBatch.Begin();
                gameStates[currentGameState].Draw(spriteBatch);
                spriteBatch.End();
            }

            //Draw console
            GameConsole.Draw(spriteBatch);
        }
        public static void AddGameState(GameState gameState)
        {
            if (gameStates.ContainsKey(gameState.Name))
            {
                GameConsole.WriteLine("The gamestate '" + gameState.Name + "' already exists.");
            }
            else
            {
                gameStates.Add(gameState.Name, gameState);
            }
        }
        public static void SetGameState(string name)
        {
            if (gameStates.ContainsKey(name))
            {
                currentGameState = name;
            }
        }
        public static void ApplyChanges()
        {
            graphicsManager.PreferredBackBufferHeight = Manager.SettingInt("screen_height", defaultScreenSize.Y);
            graphicsManager.PreferredBackBufferWidth = Manager.SettingInt("screen_width", defaultScreenSize.X);
            graphicsManager.IsFullScreen = Manager.SettingInt("screen_isFullScreen") == 1;
            graphicsManager.ApplyChanges();
            screen = new Vector2(graphicsDevice.Viewport.Width, graphicsDevice.Viewport.Height);
            screenRect = new Rectangle(0, 0, graphicsDevice.Viewport.Width, graphicsDevice.Viewport.Height);
        }
        static void ScreenShot()
        {
            takeScreenshot = false;

            int width = (int)screen.X;
            int height = (int)screen.Y;

            RenderTarget2D screenshot = new RenderTarget2D(graphicsDevice,width,height);
            GraphicsDevice.SetRenderTarget(screenshot); // rendering to the render target

            Draw();

            GraphicsDevice.SetRenderTarget(null); // finished with render target

            int suffix = 0;
            while (File.Exists(screenshotFolder + DateTime.Now.ToString("yyyy-MM-dd ") + suffix + ".png"))
            {
                suffix++;
            }

            using (FileStream fs = new FileStream(screenshotFolder + DateTime.Now.ToString("yyyy-MM-dd ") + suffix + ".png", FileMode.OpenOrCreate))
            {
                screenshot.SaveAsPng(fs, width, height); // save render target to disk
            }
        }
        public static string Setting(string name, string defaultValue = "", bool replaceCurrent = false)
        {
            if (!settings.ContainsKey(name))
                settings.Add(name, defaultValue);
            if (replaceCurrent)
                settings[name] = defaultValue;

            return settings[name];
        }
        public static int SettingInt(string name, int defaultValue = 0, bool replaceCurrent = false)
        {
            if (!settings.ContainsKey(name))
                settings.Add(name, defaultValue.ToString());
            if (replaceCurrent)
                settings[name] = defaultValue.ToString();

            int result = 0;
            if (Int32.TryParse(settings[name], out result))
            {
                return result;
            }
            else
            {
                throw new ArgumentException("This setting is not an int");
            }
        }
        public static void DefineControl(string controlName, string settingKey, Keys defaultValue)
        {
            if (!controls.ContainsKey(controlName))
            {
                if (!settings.ContainsKey(settingKey))
                {
                    settings[settingKey] = ((int)defaultValue).ToString();
                }
                controls[controlName] = settingKey;
            }
        }
        public static void LoadSettings()
        {
            if (File.Exists(settingsFile))
            {
                XDocument doc = XDocument.Load(settingsFile);

                XElement root = doc.Element("settings");

                foreach (XElement child in root.Elements())
                {
                    settings[child.Name.ToString()] = child.Value;
                }
            }

            ApplyChanges();
        }
        public static void SaveSettings()
        {
            XDocument doc = new XDocument();
            XElement root = new XElement("settings");

            foreach (KeyValuePair<string, string> pair in settings)
            {
                XElement element = new XElement(pair.Key);
                element.SetValue(pair.Value);
                root.Add(element);
            }

            doc.Add(root);
            doc.Save(settingsFile);
        }
        public static void Exit()
        {
            SaveSettings();
        }
        #region InputEvents
        static void InputSystem_MouseWheel(object sender, MouseEventArgs e)
        {
            gameStates[currentGameState].OnMouseWheel(e);
        }
        static void InputSystem_MouseUp(object sender, MouseEventArgs e)
        {
            gameStates[currentGameState].OnMouseUp(e);
        }
        static void InputSystem_MouseMove(object sender, MouseEventArgs e)
        {
            gameStates[currentGameState].OnMouseMove(e);
        }
        static void InputSystem_MouseHover(object sender, MouseEventArgs e)
        {
            gameStates[currentGameState].OnMouseHover(e);
        }
        static void InputSystem_MouseDown(object sender, MouseEventArgs e)
        {
            gameStates[currentGameState].OnMouseDown(e);
        }
        static void InputSystem_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            gameStates[currentGameState].OnDoubleClick(e);
        }
        static void InputSystem_KeyUp(object sender, KeyEventArgs e)
        {
            gameStates[currentGameState].OnKeyUp(e);
        }
        static void InputSystem_KeyDown(object sender, KeyEventArgs e)
        {
            if ((int)e.KeyCode == Manager.SettingInt("keys_screenshot"))
                takeScreenshot = true;
            gameStates[currentGameState].OnKeyDown(e);
        }
        static void InputSystem_CharEntered(object sender, CharacterEventArgs e)
        {
            gameStates[currentGameState].OnCharacter(e);
        }
        #endregion

        #endregion

        #region Properties
        public static GameTime GameTime { get { return gameTime; } set { gameTime = value; } }
        public static Vector2 Screen { get { return screen; } }
        public static Random Random { get { return random; } }
        public static GameState GameState
        {
            get { return gameStates[currentGameState]; }
            set
            {
                if (gameStates.ContainsKey(value.Name))
                {
                    currentGameState = value.Name;
                }
            }
        }
        public static Dictionary<string, GameState> GameStateList { get { return gameStates; } }
        public static GraphicsDevice GraphicsDevice { get { return graphicsDevice; } }
        public static Rectangle ScreenRectangle { get { return screenRect; } }
        public static Camera2DGameObject CurrentCamera { get { return currentCamera; } set { currentCamera = value; } }
        public static ContentManager Content { get { return content; } }
        public static Dictionary<string, string> SettingsList { get { return settings; } }
        public static Dictionary<string, string> Controls { get { return controls; } }
        #endregion
    }
}
